
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;

namespace Gltf
{
    public class Model : ModelSpec
    {
        void merge(Model rhs) { }

        public static implicit operator bool(Model foo)
        {
            return foo == null ? false : true;
        }

        internal void forEachPrimitiveInScene(int sceneID, Action<Model, Node, Mesh, MeshPrimitive, double4x4> callback)
        {
            double4x4 rootTransform = double4x4.identity;

            if (sceneID >= 0)
            {
                // Use the user-specified scene if it exists.
                if (sceneID < this.scenes.Count)
                {
                    forEachPrimitiveInSceneObject(
                      ref rootTransform,
                        this,
                        this.scenes[sceneID],
                       callback);
                }
            }
            else if (
              this.scene >= 0 &&
              this.scene < (this.scenes.Count))
            {
                // Use the default scene
                forEachPrimitiveInSceneObject(
                  ref rootTransform,
                    this,
                    this.scenes[(this.scene)],
                    callback);
            }
            else if (this.scenes.Any())
            {
                // There's no default, so use the first scene
                Scene defaultScene = this.scenes[0];
                forEachPrimitiveInSceneObject(ref rootTransform, this, defaultScene, callback);
            }
            else if (this.nodes.Any())
            {
                // No scenes at all, use the first node as the root node.
                forEachPrimitiveInNodeObject(
                  ref rootTransform,
                    this,
                    this.nodes[0],
                    callback);
            }
            else if (this.meshes.Any())
            {
                // No nodes either, show all the meshes.
                foreach (Mesh mesh in this.meshes)
                {
                    forEachPrimitiveInMeshObject(
                        rootTransform,
                        this,
                        new Node(),
                        mesh,
                        callback);
                }
            }
        }

        private void forEachPrimitiveInMeshObject(double4x4 transform, Model model, Node node, Mesh mesh, Action<Model, Node, Mesh, MeshPrimitive, double4x4> callback)
        {
            foreach (MeshPrimitive primitive in mesh.primitives)
            {
                callback(model, node, mesh, primitive, transform);
            }
        }

        private void forEachPrimitiveInSceneObject(ref double4x4 rootTransform, Model model, Scene scene, Action<Model, Node, Mesh, MeshPrimitive, double4x4> callback)
        {
            foreach (int nodeID in scene.nodes)
            {
                if (nodeID >= 0 && nodeID < model.nodes.Count)
                {
                    forEachPrimitiveInNodeObject(
                        ref rootTransform,
                        model,
                        model.nodes[(nodeID)],
                        callback);
                }
            }
        }

        private void forEachPrimitiveInNodeObject(ref double4x4 transform, Model model, Node node, Action<Model, Node, Mesh, MeshPrimitive, double4x4> callback)
        {
            double4x4 nodeTransform = transform;
            double4x4 matrix = node.matrix;
            bool4x4 isidentityMatrix = node.matrix != double4x4.identity;
            if (math.any(isidentityMatrix.c0) || math.any(isidentityMatrix.c1) || math.any(isidentityMatrix.c2) || math.any(isidentityMatrix.c3))
            {
                double4x4 nodeTransformGltf = matrix;
                nodeTransform = math.mul(nodeTransform, nodeTransformGltf);
            }
            else if (true)
            {

                double4x4 translation = double4x4.identity;
                translation[3] = new double4(
                    node.translation[0],
                    node.translation[1],
                    node.translation[2],
                    1.0);

                //TODO
                // quaternion rotationQuat = new quaternion(
                //     node.rotation[0],
                //      node.rotation[1],
                //       node.rotation[2],
                //       node.rotation[3]);

                double4x4 scale = new double4x4(
                   node.scale[0],
                   node.scale[1],
                   node.scale[2],
                   1.0
                );

                // nodeTransform =nodeTransform * translation * new double4x4(rotationQuat) * scale;
                nodeTransform = nodeTransform * translation * scale;
            }

            int meshId = node.mesh;
            if (meshId >= 0 && meshId < (model.meshes.Count))
            {
                Mesh mesh = model.meshes[(meshId)];
                forEachPrimitiveInMeshObject(nodeTransform, model, node, mesh, callback);
            }

            foreach (int childNodeId in node.children)
            {
                if (childNodeId >= 0 &&
                    childNodeId < (model.nodes.Count))
                {
                    forEachPrimitiveInNodeObject(
                       ref nodeTransform,
                        model,
                        model.nodes[(childNodeId)],
                        callback);
                }
            }
        }

        public static T getSafe<T>(List<T> pItems, int index) where T : class
        {
            if (index < 0 || index >= pItems.Count())
            {
                return null;
            }
            else
            {
                return pItems[index];
            }
        }

        internal void generateMissingNormalsSmooth()
        {
            throw new NotImplementedException();
        }

        internal int texture()
        {
            throw new NotImplementedException();
        }


    }
}

